package com.sun.androidexerciseproject.function.security;


import android.util.Base64;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * Created by Administrator on 2017/8/5 0005.
 */

public class RSA {
    public static final String ALOGRITHM_RSA = "RSA";
    public static final String KEY_RSA_PUBLIC = "RSAPublicKey";
    public static final String KEY_RSA_PRIVATE = "RSAPrivateKey";
    public static final String KEY_RSA_SINGATURE = "MD5withRSA";
    private static final int KEY_LEN = 1024;
    private Map<String, Object> mRSAMap = null;

    private RSA() {
        updateKey();
    }

    public static RSA getInstance() {
        return Holder.sRSA;
    }

    public void updateKey() {
        if (null == mRSAMap) {
            mRSAMap = new HashMap<>();
            initKey();
        } else {
            mRSAMap.clear();
            initKey();
        }
    }

    private void initKey() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALOGRITHM_RSA);
            keyPairGenerator.initialize(KEY_LEN, new SecureRandom());
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            Key publicKey = keyPair.getPublic();
            Key privateKey = keyPair.getPrivate();
            mRSAMap.put(KEY_RSA_PUBLIC, publicKey);
            mRSAMap.put(KEY_RSA_PRIVATE, privateKey);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    public String getPulicKey() {
        Key key = (Key) mRSAMap.get(KEY_RSA_PUBLIC);
        return encryptBase64(key.getEncoded());
    }

    public String getPrivateKey() {
        Key key = (Key) mRSAMap.get(KEY_RSA_PRIVATE);
        return encryptBase64(key.getEncoded());
    }

    private String encryptBase64(byte[] key) {
        return Base64.encodeToString(key, Base64.DEFAULT);
    }

    private byte[] decryptBase64(String key) {
        return Base64.decode(key, Base64.DEFAULT);
    }

    /**
     * encrypt plaint text with public key
     *
     * @param plaintText
     * @return encrypt string
     */
    public String encrypyByPublic(String plaintText) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decryptBase64(getPulicKey()));
        try {
            KeyFactory factory = KeyFactory.getInstance(ALOGRITHM_RSA);
            Key publicKey = factory.generatePublic(keySpec);
            Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return encryptBase64(cipher.doFinal(plaintText.getBytes()));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * decrpt cipher text with private key
     *
     * @param cipherText
     * @return
     */
    public String decryptByPrivate(String cipherText) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decryptBase64(getPrivateKey()));
        try {
            KeyFactory factory = KeyFactory.getInstance(ALOGRITHM_RSA);
            Key privateKey = factory.generatePrivate(keySpec);
            Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(cipher.doFinal(decryptBase64(cipherText)));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String sign(String plaintText) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decryptBase64(getPrivateKey()));
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALOGRITHM_RSA);
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            Signature signature = Signature.getInstance(KEY_RSA_SINGATURE);
            signature.initSign(privateKey);
            signature.update(plaintText.getBytes());
            return encryptBase64(signature.sign());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean verify(String cipherText, String sign) {
        boolean ret = false;
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(cipherText.getBytes());
        try {
            KeyFactory factory = KeyFactory.getInstance(ALOGRITHM_RSA);
            PublicKey publicKey = factory.generatePublic(keySpec);
            Signature signature = Signature.getInstance(KEY_RSA_SINGATURE);
            signature.initVerify(publicKey);
            signature.update(cipherText.getBytes());
            ret = signature.verify(decryptBase64(sign));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        }
        return ret;
    }

    private static final class Holder {
        private static final RSA sRSA = new RSA();
    }
}

